<template>
  <a
    ref="linkRef"
    :href="href"
    target="_blank"
    class="hover-link"
    :style="{ color }"
    @mouseenter="onHover"
    @mouseleave="onLeave"
  >
    <span ref="textRef">{{ text }}</span>
    <div ref="underlineContainer" class="underline-container"></div>
  </a>
</template>

<script setup>
import { ref, watch, nextTick, onMounted, onBeforeUnmount } from "vue";

const props = defineProps({
  text: { type: String, required: true },
  href: { type: String, required: true },
  color: { type: String, default: "#000" },
  duration: { type: Number, default: 0.12 }, // 每行动画时间
  lineHeightOffset: { type: Number, default: 0 }, //下划线与文本距离
});

const linkRef = ref(null);
const textRef = ref(null);
const underlineContainer = ref(null);
let isHovering = false;
let resizeObserver;

// 等待 rect 稳定
const waitForStableRects = async (maxFrames = 30) => {
  const textEl = textRef.value;
  if (!textEl) return;
  let prevRects = [];
  let stableCount = 0;
  let frame = 0;
  while (stableCount < 3 && frame < maxFrames) {
    await new Promise((r) => requestAnimationFrame(r));
    const rects = Array.from(textEl.getClientRects())
      .filter((r) => r.width > 1)
      .map((r) => [r.left, r.right, r.top, r.bottom]);
    if (JSON.stringify(rects) === JSON.stringify(prevRects)) {
      stableCount++;
    } else {
      stableCount = 0;
      prevRects = rects;
    }
    frame++;
  }
};

// 构建下划线
const buildUnderlines = async () => {
  await nextTick();
  await waitForStableRects();

  const textEl = textRef.value;
  const container = underlineContainer.value;
  const linkEl = linkRef.value;
  if (!textEl || !container || !linkEl) return;

  container.innerHTML = "";

  const range = document.createRange();
  range.selectNodeContents(textEl);
  const rects = Array.from(range.getClientRects()).filter((r) => r.width > 1);

  const parentRect = linkEl.getBoundingClientRect();

  // 按 top 分组，合并同一行
  const lines = [];
  rects.forEach((r) => {
    const last = lines[lines.length - 1];
    if (last && Math.abs(r.top - last.top) < 2) {
      last.left = Math.min(last.left, r.left);
      last.right = Math.max(last.right, r.right);
      last.bottom = Math.max(last.bottom, r.bottom);
    } else {
      lines.push({ left: r.left, right: r.right, top: r.top, bottom: r.bottom });
    }
  });

  // 生成下划线
  lines.forEach((r) => {
    const line = document.createElement("span");
    line.className = "underline-line";
    line.style.position = "absolute";
    line.style.left = `${r.left - parentRect.left}px`;
    line.style.top = `${r.bottom - parentRect.top - props.lineHeightOffset}px`;
    line.style.width = `${r.right - r.left}px`;
    line.style.height = "2px";
    line.style.backgroundColor = "#78f2ab";
    line.style.transform = "scaleX(0)";
    line.style.transformOrigin = "left";
    line.style.transition = `transform ${props.duration}s ease`;
    container.appendChild(line);
  });
};

// hover 展开
const onHover = () => {
  if (isHovering) return;
  isHovering = true;

  const lines = Array.from(underlineContainer.value.children);
  lines.forEach((line, i) => {
    line.style.transitionDelay = `${i * props.duration}s`;
    line.style.transform = "scaleX(1)";
  });
};

// hover 收起
const onLeave = () => {
  if (!isHovering) return;
  isHovering = false;

  const lines = Array.from(underlineContainer.value.children);
  const n = lines.length;
  lines.forEach((line, i) => {
    line.style.transitionDelay = `${(n - i - 1) * props.duration}s`;
    line.style.transform = "scaleX(0)";
  });
};

// 文本变化或窗口大小时重建
const rebuild = async () => {
  const lines = Array.from(underlineContainer.value?.children || []);
  lines.forEach((line) => (line.style.transform = "scaleX(0)")); // 先收起
  await buildUnderlines();
};

watch(() => props.text, rebuild);

onMounted(() => {
  nextTick(buildUnderlines);
  window.addEventListener("resize", rebuild);
  resizeObserver = new ResizeObserver(() => {
    rebuild();
  });
  if (linkRef.value) resizeObserver.observe(linkRef.value);
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", rebuild);
  if (resizeObserver) resizeObserver.disconnect();
});
</script>

<style scoped>
.hover-link {
  position: relative;
  display: inline-block;
  text-decoration: none;
  cursor: pointer;
  line-height: 1.5;
}

.underline-container {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}
</style>
